#include "mat4.h"

Mat4x4 operator + (const Mat4x4& m1, const Mat4x4& m2) {
    Mat4x4 result;
    for (int r = 0; r < Mat4x4::row; ++r) {
        for (int c = 0; c < Mat4x4::col; ++c) {
            result(r, c) = m1(r, c) + m2(r, c);
        }
    }
    return result;
}

Mat4x4 operator - (const Mat4x4& m1, const Mat4x4& m2) {
    Mat4x4 result;
    for (int r = 0; r < Mat4x4::row; ++r) {
        for (int c = 0; c < Mat4x4::col; ++c) {
            result(r, c) = m1(r, c) - m2(r, c);
        }
    }
    return result;
}

Mat4x4 operator * (const Mat4x4& m1, float value) {
    Mat4x4 result;
    for (int r = 0; r < Mat4x4::row; ++r) {
        for (int c = 0; c < Mat4x4::col; ++c) {
            result(r, c) = m1(r, c) - value;
        }
    }
    return result;
}

Mat4x4 operator * (float value, const Mat4x4& m1) {
    return m1 * value;
}

Mat4x4 operator * (const Mat4x4& m1, const Mat4x4& m2) {
    Mat4x4 result = Mat4x4::Zero();

    result.data[0]  = m1.data[0] * m2.data[0] + m1.data[1] * m2.data[4] + m1.data[2] * m2.data[8]  + m1.data[3] * m2.data[12];
    result.data[1]  = m1.data[0] * m2.data[1] + m1.data[1] * m2.data[5] + m1.data[2] * m2.data[9]  + m1.data[3] * m2.data[13];
    result.data[2]  = m1.data[0] * m2.data[2] + m1.data[1] * m2.data[6] + m1.data[2] * m2.data[10] + m1.data[3] * m2.data[14];
    result.data[3]  = m1.data[0] * m2.data[3] + m1.data[1] * m2.data[7] + m1.data[2] * m2.data[11] + m1.data[3] * m2.data[15];

    result.data[4]  = m1.data[4] * m2.data[0] + m1.data[5] * m2.data[4] + m1.data[6] * m2.data[8]  + m1.data[7] * m2.data[12];
    result.data[5]  = m1.data[4] * m2.data[1] + m1.data[5] * m2.data[5] + m1.data[6] * m2.data[9]  + m1.data[7] * m2.data[13];
    result.data[6]  = m1.data[4] * m2.data[2] + m1.data[5] * m2.data[6] + m1.data[6] * m2.data[10] + m1.data[7] * m2.data[14];
    result.data[7]  = m1.data[4] * m2.data[3] + m1.data[5] * m2.data[7] + m1.data[6] * m2.data[11] + m1.data[7] * m2.data[15];

    result.data[8]  = m1.data[8] * m2.data[0] + m1.data[9] * m2.data[4] + m1.data[10] * m2.data[8]  + m1.data[11] * m2.data[12];
    result.data[9]  = m1.data[8] * m2.data[1] + m1.data[9] * m2.data[5] + m1.data[10] * m2.data[9]  + m1.data[11] * m2.data[13];
    result.data[10] = m1.data[8] * m2.data[2] + m1.data[9] * m2.data[6] + m1.data[10] * m2.data[10] + m1.data[11] * m2.data[14];
    result.data[11] = m1.data[8] * m2.data[3] + m1.data[9] * m2.data[7] + m1.data[10] * m2.data[11] + m1.data[11] * m2.data[15];

    result.data[12] = m1.data[12] * m2.data[0] + m1.data[13] * m2.data[4] + m1.data[14] * m2.data[8]  + m1.data[15] * m2.data[12];
    result.data[13] = m1.data[12] * m2.data[1] + m1.data[13] * m2.data[5] + m1.data[14] * m2.data[9]  + m1.data[15] * m2.data[13];
    result.data[14] = m1.data[12] * m2.data[2] + m1.data[13] * m2.data[6] + m1.data[14] * m2.data[10] + m1.data[15] * m2.data[14];
    result.data[15] = m1.data[12] * m2.data[3] + m1.data[13] * m2.data[7] + m1.data[14] * m2.data[11] + m1.data[15] * m2.data[15];

    return result;
}

Vec4f operator * (const Mat4x4& m1, const Vec4f& v1) {
    Vec4f result;
    result.x = v1.data[0] * m1.data[0] + v1.data[1] * m1.data[1] + v1.data[2] * m1.data[2]  + v1.data[3] * m1.data[3];
    result.y = v1.data[0] * m1.data[4] + v1.data[1] * m1.data[5] + v1.data[2] * m1.data[6]  + v1.data[3] * m1.data[7];
    result.z = v1.data[0] * m1.data[8] + v1.data[1] * m1.data[9] + v1.data[2] * m1.data[10]  + v1.data[3] * m1.data[11];
    result.w = v1.data[0] * m1.data[12] + v1.data[1] * m1.data[13] + v1.data[2] * m1.data[14]  + v1.data[3] * m1.data[15];
    return result;
}

Mat4x4 operator / (const Mat4x4& m1, float value) {
    assert(value != 0.0f);
    return m1 * (1 / value);
}

Mat4x4 transpose(const Mat4x4& mat) {
    Mat4x4 result;
    for (int r = 0; r < Mat4x4::row; ++r) {
        for (int c = 0; c < Mat4x4::col; ++c) {
            result(r, c) = mat(c, r);
        }
    }
    return result;
}